home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / LIBRARY / PAS_0693 / DIGITAL.PAS < prev    next >
Pascal/Delphi Source File  |  1993-06-30  |  16KB  |  490 lines

  1. {─ Fido Pascal Conference ────────────────────────────────────────────── PASCAL ─
  2. Msg  : 583 of 587                                                               
  3. From : David Dahl                          1:272/38.0           06 Jun 93  02:45 
  4. To   : All                                                                       
  5. Subj : [1/2] Sound Playing Routines                                           
  6. ────────────────────────────────────────────────────────────────────────────────
  7.         I've gotten tired of writing these routines and have gone 
  8. on to other projects so I don't have time to work on them now.  I 
  9. figured others may get some use out of them though.  They're not 
  10. totally done yet, but what is there does work (as far as I can 
  11. tell).  They support playing digitized sound (signed or unsigned) 
  12. at sample rates from 18hz to 44.1khz (at least on my 386sx/25), 
  13. on the PC Speaker (polled), LPT DACs (1-4) or Adlib FM channels.  
  14. I was planning on adding Sound Blaster DAC, Gravis UltraSound, 
  15. and PC Speaker (pulse width modulated) support.  I also planned 
  16. on adding VOC support.  I may add those at a later date, but no 
  17. promises.  I'll release any new updates (if there are any) 
  18. through the PDN since these routines are a little long (this will 
  19. be the ONLY post of these routines in this echo).  I haven't 
  20. tested the LPT DAC routines, so could someone who has an LPT DAC 
  21. please test them and let me know if they work?  (They SHOULD
  22. work, but you never know.)  These routines work for me under
  23. Turbo Pascal V6.0 on my 386sx/25.}
  24.  
  25. Unit Digital;
  26. (*************************************************************************)
  27. (*                                                                       *)
  28. (*  Programmed by David Dahl                                             *)
  29. (*  This Unit and all routines are PUBLIC DOMAIN.                        *)
  30. (*                                                                       *)
  31. (*  Special thanks to Emil Gilliam for information (and code!) on Adlib  *)
  32. (*  digital output.                                                      *)
  33. (*                                                                       *)
  34. (*  If you use any of these routines in your own programs, I would       *)
  35. (*  appreciate an acknowledgement in the docs and/or program... and I'm  *)
  36. (*  sure Mr. Gilliam wouldn't object to having his name mentioned, too.  *)
  37. (*                                                                       *)
  38. (*************************************************************************)
  39. Interface
  40.  
  41. Const BufSize       = 2048;
  42.  
  43. Type  BufferType = Array[1 .. BufSize] of Byte;
  44.       BufPointer = ^BufferType;
  45.  
  46.       DeviceType = (LPT1, LPT2, LPT3, LPT4, PcSpeaker, PCSpeakPW,
  47.                     Adlib, SoundBlaster, UltraSound);
  48.  
  49. Var   DonePlaying : Boolean;
  50.  
  51. Procedure SetOutPutDevice (DeviceName    : DeviceType;
  52.                            SignedSamples : Boolean    );
  53. Procedure SetPlaySpeed    (Speed : LongInt);
  54.  
  55. Procedure PlayRAWSoundFile (FileName   : String;
  56.                             SampleRate : Word   );
  57. Function  LoadBuffer       (Var F    : File;
  58.                             Var BufP : BufPointer) : Word;
  59. Procedure PlayBuffer       (BufPtr : BufPointer;
  60.                             Size   : Word       );
  61.  
  62. Procedure HaltPlaying;
  63. Procedure CleanUp;
  64.  
  65. Implementation
  66.  
  67. Uses CRT;
  68.  
  69. Const C8253ModeControl   = $43;
  70.       C8253Channel       : Array[0..2] of Byte = ($40, $41, $42);
  71.       C8253OperatingFreq = 1193180;
  72.  
  73.       C8259Command       = $20;
  74.  
  75.       TimerInterrupt     = $08;
  76.  
  77.       AdlibIndex         = $388;
  78.       AdlibReg           = $389;
  79.  
  80. Type  ZeroAndOne = 0 .. 1;
  81.  
  82. Var   DataLength  : Word;
  83.       Buffer      : BufPointer;
  84.  
  85.       LPTAddress  : Word;
  86.       LPTPort     : Array[1 .. 4] of Word Absolute $0040:$0008;
  87.  
  88.       OldTimerInterrupt : Pointer;
  89.  
  90.       InterruptVector   : Array[0..255] of Pointer Absolute $0000:$0000;
  91.  
  92. {=[ Misc Procedures ]=====================================================}
  93. {-[ Clear Interrupt Flag (Disable Maskable Interrupts) ]------------------}
  94. Procedure CLI;
  95. Inline($FA);
  96. {-[ Set Interrupt Flag ]--------------------------------------------------}
  97. Procedure STI;
  98. Inline($FB);
  99. {=[ Initialize Sound Devices ]============================================}
  100. {-[ Initialize Adlib FM For Digital Output ]------------------------------}
  101. Procedure InitializeAdlib;
  102.  
  103. Var TempInt : Pointer;
  104.  
  105.   Procedure Adlib (Reg, Data : Byte); Assembler;
  106.   Asm
  107.      mov  dx, AdlibIndex            { Adlib index port }
  108.      mov  al, Reg
  109.  
  110.      out  dx,al                     { Set the index }
  111.  
  112.      { Wait for hardware to respond }
  113.      in al,dx; in al,dx; in al,dx; in al,dx; in al,dx; in al,dx
  114.  
  115.      inc  dx                        { Adlib register port }
  116.      mov  al, Data
  117.      out  dx,al                     { Set the register value }
  118.  
  119.      dec  dx                        { Adlib index port }
  120.  
  121.      { Wait for hardware to respond }
  122.      in al,dx; in al,dx; in al,dx; in al,dx; in al,dx
  123.      in al,dx; in al,dx; in al,dx; in al,dx; in al,dx
  124.      in al,dx; in al,dx; in al,dx; in al,dx; in al,dx
  125.      in al,dx; in al,dx; in al,dx; in al,dx; in al,dx
  126.      in al,dx; in al,dx; in al,dx; in al,dx; in al,dx
  127.      in al,dx; in al,dx; in al,dx; in al,dx; in al,dx
  128.      in al,dx; in al,dx; in al,dx; in al,dx; in al,dx
  129.  
  130.   End;
  131.  
  132. Begin
  133.      Adlib ($00, $00);    { Set Adlib test Register }
  134.      Adlib ($20, $21);    { Operator 0: MULTI=1, AM=VIB=KSR=0, EG=1 }
  135.      Adlib ($60, $F0);    { Attack = 15, Decay = 0 }
  136.      Adlib ($80, $F0);    { Sustain = 15, Release = 0 }
  137.      Adlib ($C0, $01);    { Feedback = 0, Additive Synthesis = 1 }
  138.      Adlib ($E0, $00);    { Waveform = Sine Wave }
  139.      Adlib ($43, $3F);    { Operator 4: Total Level = 63, Attenuation = 0 }
  140.      Adlib ($B0, $01);    { Fnumber = 399 }
  141.      Adlib ($A0, $8F);
  142.      Adlib ($B0, $2E);    { FNumber = 143, Key-On }
  143.  
  144. {   Wait for the operator's sine wave to get to top and then stop it there
  145.     That way, we have an operator who's wave is stuck at the top, and we can
  146.     play digitized sound by changing it's total level (volume) register. }
  147.  
  148.     Asm
  149.        mov  al,0                      { Get timer 0 value into DX }
  150.        out  43h,al
  151.        jmp  @Delay1
  152.  
  153.     @Delay1:
  154.        in   al,40h
  155.  
  156.        mov  dl,al
  157.        jmp  @Delay2
  158.  
  159.     @Delay2:
  160.        in   al,40h
  161.  
  162.        mov  dh,al
  163.        sub  dx,952h                   { Target value }
  164.  
  165.        @wait_loop:
  166.          mov  al,0                    { Get timer 0 value into BX }
  167.          out  43h,al
  168.          jmp  @Delay3
  169.  
  170.        @Delay3:
  171.          in   al,40h
  172.  
  173.          mov  bl,al
  174.          jmp  @Delay4
  175.  
  176.        @Delay4:
  177.          in   al,40h
  178.  
  179.          mov  bh,al
  180.          cmp  bx,dx                   { Have we waited that much time yet? }
  181.        ja   @wait_loop                { If no, then go back }
  182.  
  183.     End;
  184.  
  185. {   Now that the sine wave is at the top, change its frequency to 0 to keep
  186.     it from moving  }
  187.  
  188.     Adlib ($B0, $20);  { F-Number = 0 }
  189.     Adlib ($A0, $00);  { Frequency = 0 }
  190.  
  191.     Port [AdlibIndex] := $40;
  192. End;
  193. {=[ Sound Device Handlers ]===============================================}
  194. Procedure PlayPCSpeaker; Interrupt;
  195. Const Counter : Word = 1;
  196. Begin
  197.      If Not(DonePlaying) Then
  198.      Begin
  199.           If Counter <= DataLength Then
  200.           Begin
  201.                Port[$61] := (Port[$61] AND 253) OR
  202.                             ((Buffer^[Counter] AND 128) SHR 6);
  203.  
  204.                Counter := Counter + 1;
  205.           End
  206.           Else
  207.           Begin
  208.                DonePlaying := True;
  209.                Counter     := 1;
  210.           End;
  211.      End;
  212.  
  213.      Port[C8259Command] := $20; { Enable Interrupts }
  214. End;
  215.  
  216. Procedure PlayPCSpeakerSigned; Interrupt;
  217. Const Counter : Word = 1;
  218. Begin
  219.      If Not(DonePlaying) Then
  220.      Begin
  221.           If Counter <= DataLength Then
  222.           Begin
  223.                Port[$61] := (Port[$61] AND 253) OR
  224.                             ((byte(shortint(Buffer^[Counter]) + 128) AND
  225.                             128) SHR 6);
  226.  
  227.                Counter := Counter + 1;
  228.           End
  229.           Else
  230.           Begin
  231.                DonePlaying := True;
  232.                Counter     := 1;
  233.           End;
  234.      End;
  235.  
  236.      Port[C8259Command] := $20; { Enable Interrupts }
  237. End;
  238.  
  239. Procedure PlayLPT; Interrupt;
  240. Const Counter : Word = 1;
  241. Begin
  242.      If Not(DonePlaying) Then
  243.      Begin
  244.           If Counter <= DataLength Then
  245.           Begin
  246.                Port[LPTAddress] := Buffer^[Counter];
  247.  
  248.                Counter := Counter + 1;
  249.           End
  250.           Else
  251.           Begin
  252.                DonePlaying := True;
  253.                Counter     := 1;
  254.           End;
  255.      End;
  256.  
  257.      Port[C8259Command] := $20; { Enable Interupts }
  258. End;
  259.  
  260. Procedure PlayLPTSigned; Interrupt;
  261. Const Counter : Word = 1;
  262. Begin
  263.      If Not(DonePlaying) Then
  264.      Begin
  265.           If Counter <= DataLength Then
  266.           Begin
  267.                Port[LPTAddress] := byte(shortint(Buffer^[Counter]) + 128);
  268.  
  269.                Counter := Counter + 1;
  270.           End
  271.           Else
  272.           Begin
  273.                DonePlaying := True;
  274.                Counter     := 1;
  275.           End;
  276.      End;
  277.  
  278.      Port[C8259Command] := $20; { Enable Interupts }
  279. End;
  280.  
  281. Procedure PlayAdlib; Interrupt;
  282. Const Counter : Word = 1;
  283. Begin
  284.      If Not(DonePlaying) Then
  285.      Begin
  286.           If Counter <= DataLength Then
  287.           Begin
  288.                Port[AdlibReg] := (Buffer^[Counter] SHR 2);
  289.                Counter := Counter + 1;
  290.           End
  291.           Else
  292.           Begin
  293.                DonePlaying := True;
  294.                Counter     := 1;
  295.           End;
  296.      End;
  297.  
  298.      Port[C8259Command] := $20; { Enable Interupts }
  299. End;
  300.  
  301. Procedure PlayAdlibSigned; Interrupt;
  302. Const Counter : Word = 1;
  303. Begin
  304.      If Not(DonePlaying) Then
  305.      Begin
  306.           If Counter <= DataLength Then
  307.           Begin
  308.                Port[AdlibReg] := byte(shortint(Buffer^[Counter]) + 128)
  309.                                  SHR 2;
  310.                Counter := Counter + 1;
  311.           End
  312.           Else
  313.           Begin
  314.                DonePlaying := True;
  315.                Counter     := 1;
  316.           End;
  317.      End;
  318.  
  319.      Port[C8259Command] := $20; { Enable Interupts }
  320. End;
  321. {=[ 8253 Timer Programming Routines ]=====================================}
  322. Procedure Set8253Channel (ChannelNumber : Byte;
  323.                           ProgramValue  : Word);
  324. Begin
  325.      Port[C8253ModeControl] := 54 OR (ChannelNumber SHL 6); { XX110110 }
  326.      Port[C8253Channel[ChannelNumber]] := Lo(ProgramValue);
  327.      Port[C8253Channel[ChannelNumber]] := Hi(ProgramValue);
  328. End;
  329. {-[ Set Clock Channel 0 (INT 8, IRQ 0) To Input Speed ]-------------------}
  330. Procedure SetPlaySpeed (Speed : LongInt);
  331. Var ProgramValue : Word;
  332. Begin
  333.      ProgramValue := C8253OperatingFreq DIV Speed;
  334.  
  335.      Set8253Channel (0, ProgramValue);
  336. End;
  337. {-[ Set Clock Channel 0 Back To 18.2 Default Value ]----------------------}
  338. Procedure SetDefaultTimerSpeed;
  339. Begin
  340.      Set8253Channel (0, 0);
  341. End;
  342. {=[ File Handling ]=======================================================}
  343. {-[ Load Buffer With Data From Raw File ]---------------------------------}
  344. Function LoadBuffer (Var F    : File;
  345.                      Var BufP : BufPointer) : Word;
  346. Var NumRead : Word;
  347. Begin
  348.      BlockRead (F, BufP^, BufSize, NumRead);
  349.  
  350.      LoadBuffer := NumRead;
  351. End;
  352. {=[ Sound Playing / Setup Routines ]======================================}
  353. {-[ Output Sound Data In Buffer ]-----------------------------------------}
  354. Procedure PlayBuffer (BufPtr : BufPointer;
  355.                       Size   : Word       );
  356. Begin
  357.      Buffer      := BufPtr;
  358.      DataLength  := Size;
  359.      DonePlaying := False;
  360. End;
  361. {-[ Halt Playing ]--------------------------------------------------------}
  362. Procedure HaltPlaying;
  363. Begin
  364.      DonePlaying := True;
  365. End;
  366.  
  367. {=[ Initialize Data ]=====================================================}
  368. Procedure InitializeData;
  369. Const CalledOnce : Boolean = False;
  370. Begin
  371.      If Not(CalledOnce) Then
  372.      Begin
  373.           DonePlaying       := True;
  374.           OldTimerInterrupt := InterruptVector[TimerInterrupt];
  375.           CalledOnce        := True;
  376.      End;
  377. End;
  378. {=[ Set Interrupt Vectors ]===============================================}
  379. {-[ Set Timer Interrupt Vector To Our Device ]----------------------------}
  380. Procedure SetOutPutDevice (DeviceName    : DeviceType;
  381.                            SignedSamples : Boolean);
  382. Begin
  383.      CLI;
  384.  
  385.      Case DeviceName of
  386.           LPT1 .. LPT4 : Begin
  387.                               LPTAddress := LPTPort[Ord(DeviceName)];
  388.                               If SignedSamples Then
  389.                                  InterruptVector[TimerInterrupt] :=
  390.                                                               @PlayLPTSigned
  391.                               Else
  392.                                  InterruptVector[TimerInterrupt] := @PlayLPT;
  393.                          End;
  394.           PCSpeaker    : If SignedSamples Then
  395.                             InterruptVector[TimerInterrupt] :=
  396.                                                         @PlayPCSpeakerSigned
  397.                          Else
  398.                             InterruptVector[TimerInterrupt] :=
  399.                                                         @PlayPCSpeaker;
  400.           Adlib        : Begin
  401.                               InitializeAdlib;
  402.  
  403.                               If SignedSamples Then
  404.                                  InterruptVector[TimerInterrupt] :=
  405.                                                             @PlayAdlibSigned
  406.                               Else
  407.                                  InterruptVector[TimerInterrupt] :=
  408.                                                             @PlayAdlib;
  409.                          End;
  410.      Else
  411.          Begin
  412.             STI;
  413.  
  414.             Writeln;
  415.             Writeln ('That Sound Device Is Not Supported In This Version.');
  416.             Writeln ('Using PC Speaker In Polled Mode Instead.');
  417.  
  418.             CLI;
  419.             If SignedSamples Then
  420.                InterruptVector[TimerInterrupt] := @PlayPCSpeakerSigned
  421.             Else
  422.                InterruptVector[TimerInterrupt] := @PlayPCSpeaker;
  423.          End;
  424.      End;
  425.      STI;
  426. End;
  427. {-[ Set Timer Interupt Vector To Default Handler ]------------------------}
  428. Procedure SetTimerInterruptVectorDefault;
  429. Begin
  430.      CLI;
  431.  
  432.      InterruptVector [TimerInterrupt] := OldTimerInterrupt;
  433.  
  434.      STI;
  435. End;
  436.  
  437. Procedure PlayRAWSoundFile (FileName   : String;
  438.                             SampleRate : Word);
  439. Var RawDataFile : File;
  440.     SoundBuffer : Array[ZeroAndOne] of BufPointer;
  441.     BufNum      : ZeroAndOne;
  442.     Size        : Word;
  443. Begin
  444.      New(SoundBuffer[0]);
  445.      New(SoundBuffer[1]);
  446.  
  447.      SetPlaySpeed (SampleRate);
  448.  
  449.      Assign (RawDataFile, FileName);
  450.      Reset  (RawDataFile, 1);
  451.  
  452.      BufNum := 0;
  453.  
  454.      Size := LoadBuffer (RawDataFile, SoundBuffer[BufNum]);
  455.  
  456.      PlayBuffer (SoundBuffer[BufNum], Size);
  457.  
  458.      While Not(Eof(RawDataFile)) do
  459.      Begin
  460.           BufNum := (BufNum + 1) AND 1;
  461.  
  462.           Size := LoadBuffer (RawDataFile, SoundBuffer[BufNum]);
  463.  
  464.           Repeat Until DonePlaying;
  465.  
  466.           PlayBuffer (SoundBuffer[BufNum], Size);
  467.  
  468.      End;
  469.  
  470.      Close (RawDataFile);
  471.  
  472.      Repeat Until DonePlaying;
  473.  
  474.      SetDefaultTimerSpeed;
  475.  
  476.      Dispose (SoundBuffer[1]);
  477.      Dispose (SoundBuffer[0]);
  478. End;
  479.  
  480. {=[ MUST CALL BEFORE EXITING PROGRAM!!! ]=================================}
  481. Procedure CleanUp;
  482. Begin
  483.      SetDefaultTimerSpeed;
  484.      SetTimerInterruptVectorDefault;
  485. End;
  486. {=[ Set Up ]==============================================================}
  487. Begin
  488.      InitializeData;
  489.      NoSound;
  490. End.